package com.hp.ecs.xqa.util.orm;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Id;

import org.hibernate.SessionFactory;

import com.hp.ecs.xqa.util.StringUtil;

public class LongKeyGenerator {

	private final SessionFactory factory;

	private final Map<Class, Long> idMap = new HashMap();

	private final Map<Class, Boolean> idFlagMap = new HashMap();

	private static Map genrtMap = new HashMap();

	public static LongKeyGenerator getKeyGenerator(SessionFactory factory) {
		LongKeyGenerator keyGenrator = (LongKeyGenerator)genrtMap.get(factory);
		if (keyGenrator == null) {
			keyGenrator = new LongKeyGenerator(factory);
			genrtMap.put(factory, keyGenrator);
		}
		return keyGenrator;
	}

	private LongKeyGenerator(SessionFactory factory) {
		this.factory = factory;
	}

	public boolean checkId(Object entity) {
		if (!isLongId(entity)) {
			return false;
		}
		final Long oldId = getId(entity);
		Long id = newId(StringUtil.getVoClass(entity), oldId);
		if (oldId == null) {
			setId(entity, id);
			return true;
		}
		return false;

	}

	private Class getIdType(Object entity) {
		return factory.getClassMetadata(StringUtil.getVoClass(entity)).getIdentifierType().getReturnedClass();
	}

	private void setId(Object entity, Long id) {
		if (entity instanceof Idable) {
			((Idable)entity).setId(id);
			return;
		}
		for (Field f : entity.getClass().getDeclaredFields()) {
			if (f.getType().equals(Long.class) && f.getAnnotation(Id.class) != null) {
				f.setAccessible(true);
				try {
					f.set(entity, id);
					return;
				} catch (IllegalAccessException e) {
					throw new IllegalStateException(e);
				}
			}
		}
		throw new IllegalArgumentException("No found id of a Long field, class:" + entity.getClass());
	}

	private Long getId(Object entity) {
		if (entity instanceof Idable) {
			return ((Idable)entity).getId();
		}
		for (Field f : entity.getClass().getDeclaredFields()) {
			if (f.getType().equals(Long.class) && f.getAnnotation(Id.class) != null) {
				f.setAccessible(true);
				try {
					return (Long)f.get(entity);
				} catch (IllegalAccessException e) {
					throw new IllegalStateException(e);
				}
			}
		}
		throw new IllegalArgumentException("No found id of a Long field, class:" + entity.getClass());
	}

	private Long newId(final Class clazz, Long oldId) {
		synchronized (clazz) {
			Long keyId = idMap.get(clazz);
			if (keyId == null) {
				keyId = getMaxId(clazz);
			}
			if (oldId == null) {
				keyId++;
				idMap.put(clazz, keyId);
				return keyId;
			} else if (oldId > keyId.longValue()) {
				idMap.put(clazz, oldId);
			}
			return oldId;
		}
	}

	private Long getMaxId(Class clazz) {
		StringBuilder hql = new StringBuilder();
		final String idName = factory.getClassMetadata(clazz).getIdentifierPropertyName();
		hql.append("select max(t.").append(idName);
		hql.append(") from ").append(clazz.getName()).append(" t");
		List list = factory.getCurrentSession().createQuery(hql.toString()).list();
		if (list.isEmpty() || list.get(0) == null) {
			return 100000l;
		} else {
			return ((Number)list.get(0)).longValue();
		}
	}

	private boolean isLongId(Object entity) {
		Class clazz = StringUtil.getVoClass(entity);
		Boolean setIdFlag = idFlagMap.get(clazz);
		if (setIdFlag != null) {
			return setIdFlag.booleanValue();
		}
		final Class idType = getIdType(entity);
		boolean isLong = idType == Long.class;
		idFlagMap.put(clazz, isLong);
		return isLong;
	}
}
